/**
 * Framework for batch testing user profile models
 * Final project by Sergey Nepomnyachiy and Julia Polchin
 * Supervisor: Tsvi Kuflik
 *
 */
package models.content;

import java.util.Collections;
import java.util.HashMap;
import java.util.Vector;

/**
 * @author Sergey and Julia
 *
 */
public class IDF {
	
	private class DistanceName implements java.lang.Comparable<DistanceName> {
		/**
		 * index of user in user*item matrix
		 */
		public Integer catIndex;
		/**
		 * distance of this user to new user
		 */
		public Double distance;

		/**
		 * DistanceName constructor 
		 * @param d - distance
		 * @param c - index
		 */
		public DistanceName(Double d, Integer c) {
			this.catIndex = c;
			this.distance = d;
		}

		@SuppressWarnings("boxing")
		@Override
		public int compareTo(DistanceName arg0) {
			Double a1 = arg0.distance;
			if (this.distance <= a1)
				return -1;
			if (this.distance > a1)
				return 1;
			return 0;
		}
	}
	
	private Vector<Double> idf;
	private Vector<TFedDoc> docs;
	private boolean dirtyBit = true;
	private Integer longestDoc = 0;
	private int k;
	
	public IDF(int k) {
		this.docs = new Vector<TFedDoc>(20); 
		this.k = k;
	}
	
	public void addDoc(TFedDoc tfd) {
		if (tfd.size() > this.longestDoc)
			this.longestDoc = tfd.size();
		this.docs.add(tfd);
		this.dirtyBit = true;
	}
	
	public Vector<Double> getIdf() {
		makeIdf();
		return this.idf;
	}
	
	private void makeIdf() {
		if (! this.dirtyBit )
			return;
		this.idf = new Vector<Double>(this.longestDoc);
		double totalSize = 0.0 + docs.size();
		for (int i = 0; i < this.longestDoc; ++i) {
			double docsCount = 0.0;
			for (TFedDoc tfd : docs) {
				if ( tfd.getTF(i) > 0.0 )
					++docsCount;
			}
			if (docsCount > 0.0)
				this.idf.add(Math.log( totalSize / docsCount) );
			else 
				this.idf.add(0.0);
			
		}
		this.dirtyBit = false;
	}
	
	private Vector<Double> makeTFIDF(Vector<Double> vec) {
		makeIdf();
		for (int i=0; i<vec.size(); ++i) {
			vec.setElementAt( vec.elementAt(i)*this.idf.elementAt(i) , i);
		}
		
		for (int i =vec.size(); i< this.longestDoc; ++i)
			vec.add(0.0);
		return vec;
	}
	
	public Integer predictCategory(TFedDoc newDoc) {
		Vector<Double> nDoc = makeTFIDF(newDoc.getCopy());
		Vector<DistanceName> distances = new Vector<DistanceName>(docs.size());
		
		for (TFedDoc train : this.docs) {
			Vector<Double> tDoc = makeTFIDF(train.getCopy());
			distances.add(new DistanceName(Formulae.cosine(tDoc, nDoc) ,train.getCatId()));
		}
		Collections.sort(distances);
		
		HashMap<Integer,Integer> categoryHits = new HashMap<Integer,Integer>();
		for (int i = 0 ; i < Math.min(k, distances.size()); ++i ) {
			Integer catIndex = distances.elementAt(i).catIndex;
			if (categoryHits.containsKey( catIndex ))
				categoryHits.put(catIndex, categoryHits.get(catIndex) +1 );
			else 
				categoryHits.put(catIndex, 1 );
		}
		
		Integer max = -1;
		Integer category = -1; 
		for (Integer cat : categoryHits.keySet()) {
			
			if ( categoryHits.get(cat) > max) {
				category  = cat;
				max = categoryHits.get(cat);
			}
		}
		
		return category;
	}
}
